package ie.lawlor.amvc;

import ie.lawlor.amvc.controller.Controller;
import ie.lawlor.amvc.controller.ControllerEventWrapper;
import ie.lawlor.amvc.event.Event;
import ie.lawlor.amvc.event.IEventSink;

import java.io.Serializable;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;

/**
 * Base class for all triads. This class is not technically abstract 
 * but the constructor is protected.  
 * @author blawlor
 *
 */
public class Triad implements IEventSink, Serializable {
	
	private static final String DEFAULT_NAME = "<noname>";
	protected IModel model;
	// Some triads can have null views.
	protected IView view;
	protected Controller controller;
	protected String name;
	
	/**
	 * @return Returns the controller.
	 */
	public Controller getController() {
		return controller;
	}
	/**
	 * @param controller The controller to set.
	 */
	public void setController(Controller controller) {
		this.controller = controller;
        if (controller != null && model != null){
	        controller.setModel(model);
			model.setController(controller);
        }
		if (controller != null && view != null){
			controller.setView(view);
			view.setController(controller);
		}
        if (controller != null && name != null){
        	controller.setName(name);
        }
	}
	/**
	 * @return Returns the model.
	 */
	public IModel getModel() {
		return model;
	}
	/**
	 * @param model The model to set.
	 */
	public void setModel(IModel model) {
		this.model = model;
        if (model != null && controller != null){
	        controller.setModel(model);
			model.setController(controller);
        }
	}
	/**
	 * @param view The view to set.
	 */
	public void setView(IView view) {
		this.view = view;
		if (view != null && controller != null){
			controller.setView(view);
			view.setController(controller);
		}
	}
    /**
     * This constructor is only used by derived classes. 
     * @param model
     * @param view
     * @param controller
     */
    protected Triad(String name, IModel model, IView view, Controller controller) {
    	if (view == null || controller == null){
    		throw new IllegalArgumentException("Cannot create a triad with a null view or controller");
    	}
    	if (name == null || name.equals("")){
			throw new IllegalArgumentException("Cannot create a triad with a null or empty name");
    	}
    	this.name = name;
        this.model = model;
        this.view = view;
        this.controller = controller;
        controller.setName(name);
        if (model != null){
	        controller.setModel(model);
			model.setController(controller);
        }
        controller.setView(view);
        view.setController(controller);
    }
    
    protected Triad(String name, IModel model, Controller controller) {
    	if (controller == null){
    		throw new IllegalArgumentException("Cannot create a triad with a null view or controller");
    	}
    	if (name == null || name.equals("")){
			throw new IllegalArgumentException("Cannot create a triad with a null or empty name");
    	}
    	this.name = name;
        this.model = model;
        this.controller = controller;
        controller.setName(name);
        if (model != null){
	        controller.setModel(model);
			model.setController(controller);
        }

    }
    
    protected Triad() {
    }
    
    /*
     * This method is where the relationship between the parent
     * and child triads is formed. This includes the relationship between 
     * views, which can be complex.
     * Parents make have many subviews (Split panels, views with multiple 
     * panels)
     * Should the mapping be name-based?
     * How simply can we represent the mapping?
     * 1) Default - child triad's view is a child of 'first' parent view?
     * 
     * Child views can be added as fixed elements, or as child dialogs, or even
     * popup menus. Triad instances should be able to specify whether they are
     * dialogs (so don't require real-estate in the parent view) and if so, 
     * whether they are modal or not. Use different add methods? 
     * 
     */
    public void addChild(Triad child){
    	System.out.println("Adding " + child.getName() + " to " + getName());
    	controller.addChild(child.getName(), child.controller);
    	view.addChild(child.view);
    }
    
	public void addChild(Triad child, String parentViewName){
    	System.out.println("Adding " + child.getName() + " to " + getName());
		controller.addChild(child.getName(), child.controller);
		view.addChild(child.view, parentViewName);
	}
    
    //TODO Replace this cast and catch mechanism with a DialogEnabledTriad class    
	public void addDialogChild(Triad child){
    	System.out.println("Adding " + child.getName() + " as dialog to " + getName());
		controller.addChild(child.getName(), child.controller);
		try {
			view.addDialogChild((IDialogEnabledView)child.view);
		} catch (ClassCastException e){
			e.printStackTrace();
			throw new IllegalArgumentException("Child triad's view must be dialog-enabled");
		}
	}
    
    public void eventIn(Event e) {
//    	SwingUtilities.invokeLater(new EventRunnable(e));
      e.setSourceController(controller);
      controller.eventIn(e);
    }
    
    /**
     * @return
     */
    public String getName() {
        return name;
    }
    
    
	/**
	 * @param name The name to set.
	 */
	public void setName(String name) {
		this.name = name;
		if (controller != null){
			controller.setName(DEFAULT_NAME);
		}
	}
	
	public void setTriadEvents(List events){
		addEventWrappers(events);
	}
	
	public void setApplicationEvents(List events){
		addEventWrappers(events);
	}

	public void setInputEvents(Map events){
		controller.addInputEvents(events);
	}

	public void setOutputEvents(Map events){
		controller.addOutputEvents(events);
	}

	private void addEventWrappers(List events) {
		for (ListIterator itr = events.listIterator(); itr.hasNext();){
			ControllerEventWrapper eventWrapper = (ControllerEventWrapper)itr.next();
			eventWrapper.addToController(controller);
		}
	}

	public void setChildren(List children){
		for (ListIterator itr = children.listIterator(); itr.hasNext();){
			ChildTriad child = (ChildTriad)itr.next();
			child.addTo(this);
		}
	}
	
  public class EventRunnable implements Runnable {
  	Event e;
  	public EventRunnable(Event e){
  		this.e = e;
  	}
		public void run(){
			e.setSourceController(controller);
			controller.eventIn(e);
		}
  }

	/**
	 * @return Returns the view.
	 */
	public IView getView() {
		return view;
	}
}
